SQSデッドレターキューにメッセージが入ったことを検知するメトリクスはどれが良いのか

SQSデッドレターキューにメッセージが入ったことを検知するメトリクスはどれが良いのか

NumberOfMessagesSentではなくてApproximateNumberOfMessagesVisibleを
Clock Icon2020.04.01

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

デッドレターキュー(以下、DLQ)とは

正常に処理できないSQSメッセージを移動(退避)させるキューのことをいいます。この機能を使うと以下のようなメリットがあります。

  • 問題のあるメッセージを分離して、問題の原因を調査できる。
  • 元のキューのワーカーは、問題のあるメッセージを何度も受信してエラーし続けることを避けられる。そのため処理能力が向上する。

より詳細な情報は以下をご確認ください。

DLQにメッセージが入ったら知りたい

DLQにメッセージが入る=何かしらの問題が発生しているということなので、すぐに知りたいですよね。最低でも見逃すことは避けたいです。というわけで、CloudWatch AlarmでDLQにメッセージが入ったことを検知して、SNS経由でメール通知をします。

失敗例:NumberOfMessagesSentを使う

上記をTerraformで実装してみた例が以下です。
aws_cloudwatch_metric_alarmを使って、s3_put_object_notification_dlq というキューに1分間に1件以上メッセージが送信されたらdlq_recieved_message_notificationというSNSトピックにメッセージを送ります。(EメールのSNSトピックサブスクリプションも別途作成します)

resource aws_cloudwatch_metric_alarm dlq_recieved_message {
  alarm_name                = "dlq-recieved-message"
  comparison_operator       = "GreaterThanOrEqualToThreshold"
  evaluation_periods        = "1"
  metric_name               = "NumberOfMessagesSent"
  namespace                 = "AWS/SQS"
  period                    = "60"
  statistic                 = "Sum"
  threshold                 = "1"
  alarm_description         = "dead letter queue received message"
  alarm_actions             = [aws_sns_topic.dlq_recieved_message_notification.arn]
  insufficient_data_actions = []
  dimensions = {
    QueueName = aws_sqs_queue.s3_put_object_notification_dlq.name
  }
}

使用するCloudWatchメトリクスは、以下ページを参考に NumberOfMessagesSentを選びました。「キューに追加されたメッセージの数」というメトリクスなので適切そうです。

動作確認

このDLQはput-object-notification-queueというキューのDLQです。動作確認のために最大受信数を1にしてすぐメッセージがDLQに移動するようにします。

まずは移動元のキューであるput-object-notification-queueでメッセージを作成します。

コンソール上でのメッセージ表示も、そのメッセージの表示回数を加算します。というわけで先程作成したメッセージをコンソール上で表示します。

表示できました。

メッセージの表示回数がDLQのポリシーである最大受信数に達したので、DLQにメッセージが移動されました。

ここまで想定通り…あとはCloudWatch Alarmで検知してSNS経由でメールが送られてくるはずだ…と思っていたのですが、メールが送られてきません。

なぜ失敗したのか

確かにDLQはメッセージを受信しているのですが、NumberOfMessagesSent CloudWatchメトリクスに変化がありません。そのためCloudWatch Alarmは何も反応しなかったのです。

NumberOfMessagesSent CloudWatchメトリクスに変化がなかった理由ですが、DLQのドキュメントに以下記述がありました。

手動でデッドレターキューに送信したメッセージは、NumberOfMessagesSent メトリクスによってキャプチャされます。ただし、処理の試行が失敗した結果としてデッドレターキューにメッセージが送信された場合、メトリクスによってキャプチャされません。したがって、NumberOfMessagesSent と NumberOfMessagesReceived の値が異なることもあります。

ほう。ここからは私の推測ですが、どうやらキュー→DLQへのメッセージの移動は、

  • DLQにメッセージ作成
  • 元のキューからメッセージを削除

というより、

  • 元のキューのメッセージをDLQへ移動する

という感じで、DLQ機能を使わないキュー間のメッセージ移動とは少し処理内容が違うようです。それは以下ドキュメントからも見て取れます。

メッセージの有効期限は、常に元のキュー追加のタイムスタンプに基づいています。メッセージが dead-letter queue に移動されると、キュー追加のタイムスタンプは変更されません。たとえば、メッセージがデッドレターキューに移動される前に元のキューに 1 日を費やし、デッドレターキューの保存期間が 4 日に設定されている場合、メッセージは 3 日後にデッドレターキューから削除されます。したがって、デッドレターキューの保持期間を元のキューの保持期間よりも長く設定することが、常にベストプラクティスとなります。

メッセージの有効期限は、元のキューのものを引き継ぎます。DLQに新メッセージ作成というより、元キューからメッセージを移動させてる感じがします。

成功例:ApproximateNumberOfMessagesVisibleを使う

というわけで、CloudWatch Alarmで使うメトリクスを変更しました。 ApproximateNumberOfMessagesVisible=「キューから取得可能なメッセージの数」を使います。

 resource aws_cloudwatch_metric_alarm dlq_recieved_message {
   alarm_name                = "dlq-recieved-message"
   comparison_operator       = "GreaterThanOrEqualToThreshold"
   evaluation_periods        = "1"
+  metric_name               = "ApproximateNumberOfMessagesVisible"
-  metric_name               = "NumberOfMessagesSent"
   namespace                 = "AWS/SQS"
   period                    = "60"
   statistic                 = "Sum"
   threshold                 = "1"
   alarm_description         = "dead letter queue received message"
   alarm_actions             = [aws_sns_topic.dlq_recieved_message_notification.arn]
   insufficient_data_actions = []
   dimensions = {
     QueueName = aws_sqs_queue.s3_put_object_notification_dlq.name
   }
 }

この設定で無事アラートメールを受信することができました。

まとめ

  • SQSのデッドレターキューにメッセージが入ったことをアラートしたい場合、NumberOfMessagesSent メトリクスでは要件を満たせない。
    • NumberOfMessagesSentはDLQ機能によりキューに入ったメッセージをキャプチャしないため。
  • 代わりに ApproximateNumberOfMessagesVisible などの別のメトリクスを使いましょう。

あわせて読みたい

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.